package com.googlecode.imanager.client;

import java.io.Serializable;

import com.googlecode.imanager.client.style.HorzAlignment;
import com.googlecode.imanager.client.style.VertAlignment;
import com.googlecode.imanager.client.util.Anchors;
import com.googlecode.imanager.client.util.Dimension;
import com.googlecode.imanager.client.util.Rectangle;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public abstract class Display implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;
    
    private int left;
    private int top;
    private int width;
    private int height;
    private int right;   
    private int bottom;
    
    private transient int layouting;
    private transient int layoutingFlags;

    public Display() {
    }

    public Display(int width, int height) {
        this();
        changeWidth(width);
        changeHeight(height);
    }

    protected abstract Anchors getAnchors();
    
    public final void startUpdateLayout(){
        if (layouting == 0){
            layoutingFlags = 0;
        }
        layouting++;
    }
    
    public final void endUpdateLayout(){
        endUpdateLayout(false, false);
    }
    
    private void endUpdateLayout(boolean posChanged, boolean sizeChanged){        
        if (posChanged){
            layoutingFlags |= 1;
        }
        
        if (sizeChanged){
            layoutingFlags |= 2;
        }
        
        if (layouting == 1){
            layouting = 0;
            
            if ((layoutingFlags & 1) != 0){
                if ((layoutingFlags & 2) != 0){
                    boundsChanged();
                } else {
                    positionChanged();
                }
            } 
            else if ((layoutingFlags & 2) != 0){
                dimensionChanged();
            } 
        } else {
            assert (layouting > 0);
            layouting--;
        }
    }
    
    public final int getLeft() {
        return left;
    }

    public void setLeft(int left) {
        if (this.left != left){
            startUpdateLayout();
            endUpdateLayout(true, changeLeft(left));
        }
    }
    
    public final int getTop() {
        return top;
    }

    public void setTop(int top) {
        if (this.top != top){
            startUpdateLayout();
            endUpdateLayout(true, changeTop(top));
        }
    }

    public final int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        if (this.width != width){
            startUpdateLayout();
            endUpdateLayout(changeWidth(width), true);
        }
    }

    public final int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        if (this.height != height){
            startUpdateLayout();
            endUpdateLayout(changeHeight(height), true);
        }
    }

    public final int getRight() {
        return right;
    }

    public void setRight(int right) {
        if (this.right != right){
            startUpdateLayout();
            endUpdateLayout(true, changeRight(right));
        }
    }

    public final int getBottom() {
        return bottom;
    }

    public void setBottom(int bottom) {
        if (this.bottom != bottom){
            startUpdateLayout();
            endUpdateLayout(true, changeBottom(bottom));
        }
    }  
    
    public final Dimension getSize(){
        return new Dimension(width, height);
    }

    public void setSize(int width, int height) {
        boolean boundsChanged = false;
        boolean sizeChanged = false;

        startUpdateLayout();
        
        if (this.width != width){
            boundsChanged |= changeWidth(width);
            sizeChanged = true;
        }
        if (this.height != height){
            boundsChanged |= changeHeight(height);
            sizeChanged = true;
        }
        
        endUpdateLayout(boundsChanged, boundsChanged | sizeChanged);
    }
    
    public void setLeftTop(int left, int top) {
        boolean boundsChanged = false;
        boolean posChanged = false;
        
        startUpdateLayout();
        
        if (this.left != left){
            boundsChanged |= changeLeft(left);
            posChanged = true;
        }
        if (this.top != top){
            boundsChanged |= changeTop(top);
            posChanged = true;
        }
        
        endUpdateLayout(boundsChanged | posChanged, boundsChanged);
    }

    public void setTopRight(int top, int right) {
        boolean boundsChanged = false;
        boolean posChanged = false;

        startUpdateLayout();
        
        if (this.right != right){
            boundsChanged |= changeRight(right);
            posChanged = true;
        }
        if (this.top != top){
            boundsChanged |= changeTop(top);
            posChanged = true;
        }
        
        endUpdateLayout(boundsChanged | posChanged, boundsChanged);
    }

    public void setRightBottom(int right, int bottom) {
        boolean boundsChanged = false;
        boolean posChanged = false;
        
        startUpdateLayout();
        
        if (this.right != right){
            boundsChanged |= changeRight(right);
            posChanged = true;
        }
        if (this.bottom != bottom){
            boundsChanged |= changeBottom(bottom);
            posChanged = true;
        }
        
        endUpdateLayout(boundsChanged | posChanged, boundsChanged);
    }

    public void setBottomLeft(int bottom, int left) {
        boolean boundsChanged = false;
        boolean posChanged = false;
        
        startUpdateLayout();
        
        if (this.left != left){
            boundsChanged |= changeLeft(left);
            posChanged = true;
        }
        if (this.bottom != bottom){
            boundsChanged |= changeBottom(bottom);
            posChanged = true;
        }
        
        endUpdateLayout(boundsChanged | posChanged, boundsChanged);
    }

    public void setPosition(int left, int top, int right, int bottom) {
        boolean posChanged = false;
        boolean sizeChanged = false;
        
        startUpdateLayout();
        
        if (this.left != left){
            this.width -= left - this.left;
            this.left = left;
            posChanged = true;
            sizeChanged = true;
        }
        if (this.top != top){
            this.height -= top - this.top;
            this.top = top;
            posChanged = true;
            sizeChanged = true;
        }
        if (this.right != right){
            this.width -= right - this.right;
            this.right = right;
            sizeChanged = true;
        }
        if (this.bottom != bottom){
            this.height -= bottom - this.bottom;
            this.bottom = bottom;
            sizeChanged = true;
        }
        
        endUpdateLayout(posChanged, sizeChanged);
    }

    public final Rectangle getBounds(){
        return new Rectangle(left, top, width, height);
    }

    public void setBounds(int left, int top, int width, int height) {
        boolean posChanged = false;
        boolean sizeChanged = false;
        
        startUpdateLayout();
        
        if (this.left != left){
            this.right -= left - this.left;
            this.left = left;
            posChanged = true;
        }
        if (this.top != top){
            this.bottom -= top - this.top;
            this.top = top;
            posChanged = true;
        }
        if (this.width != width){
            this.right -= width - this.width;
            this.width = width;
            sizeChanged = true;
        }
        if (this.height != height){
            this.bottom -= height - this.height;
            this.height = height;
            sizeChanged = true;
        } 
        
        endUpdateLayout(posChanged, sizeChanged);        
    }
    
    public void setPosition(HorzAlignment horz, VertAlignment vert){
        int left = this.left;
        int top = this.top;
        int pw = (left + width + right);
        int ph = (top + height + bottom);
        
        if (horz != null){
            switch (horz) {
                case CENTER:
                    left = pw/2 - width/2;
                    break;
                case RIGHT:
                    left = pw - width;
                    break;
                default:
                    left = 0;
                    break;
            }
        }
        
        if (vert != null){
            switch (vert) {
                case MIDDLE:
                    top = ph/2 - height/2;        
                    break;
                case BOTTOM:
                    top = ph - height;
                    break;
                default:
                    top = 0;
                    break;
            }
        }
        
        setBounds(left, top, width, height);
    }
    
    public void setPosition(Integer left, Integer top, Integer right, Integer bottom) {
        int l = left != null ? left : this.left;
        int t = top != null ? top : this.top;
        int r = right != null ? right : this.right;
        int b = bottom != null ? bottom : this.bottom;
        
        if ((left != null) ^ (right != null)){
            if (left != null){
                r -= left - this.left;
            } else {
                l -= right - this.right;
            }
        }
        
        if ((top != null) ^ (bottom != null)){
            if (top != null){
                b -= top - this.top;
            } else {
                t -= bottom - this.bottom;
            }
        }
        
        setPosition(l, t, r, b);
    }

    public void setBounds(Integer left, Integer top, Integer width, Integer height) {
        int l = left != null ? left : this.left;
        int t = top != null ? top : this.top;
        int w = width != null ? width : this.width;
        int h = height != null ? height : this.height;

        if ((left != null) ^ (width != null)){
            if (left != null){
                w -= left - this.left;
            } else {
                l -= width - this.width;
            }
        }
        
        if ((top != null) ^ (height != null)){
            if (top != null){
                h -= top - this.top;
            } else {
                t -= height - this.height;
            }
        }
        
        setPosition(l, t, w, h);
    }

    protected void onParentResized(Dimension clientSize) {
        boolean sizeChanged = false;
        boolean posChanged = false;
        
        int pw = clientSize.getWidth() - (left + width + right);
        int ph = clientSize.getHeight() - (top + height + bottom);
        
        Anchors anchors = getAnchors();
        startUpdateLayout();
        
        if (pw != 0){
            if (anchors.isRight()){
                if (anchors.isLeft()){
                    width += pw;
                    sizeChanged = true;
                } else {
                    left += pw;
                    posChanged = true;
                }
            } else {
                posChanged = true;
                
                if (anchors.isLeft()){
                    right += pw;
                } else {
                    int resto = pw;
                    int metade = resto/2;
                    if (left > right){
                        left += metade;
                        right += resto - metade;
                    } else {
                        left += resto - metade;
                        right += metade;
                    }
                }
            }
        }
        
        if (ph != 0) {
            if(anchors.isBottom()){
                if (anchors.isTop()){
                    height += ph;
                    sizeChanged = true;
                } else {
                    top += ph;
                    posChanged = true;
                }
            } else {
                posChanged = true;
                
                if (anchors.isTop()){
                    bottom += ph;
                } else {
                    int resto = ph;
                    int metade = resto/2;
                    if (top > bottom){
                        top += metade;
                        bottom += resto - metade;
                    } else {
                        top += resto - metade;
                        bottom += metade;
                    }
                }
            }
        }
        
        endUpdateLayout(posChanged, sizeChanged);
    }
    
    private boolean changeLeft(int left){
        int dif = (left - this.left);
        this.left = left;
        
        if (getAnchors().isRight()){
            width -= dif;
            return true;
        } else {
            right -= dif;
            return false;
        }
    }

    private boolean changeTop(int top){
        int dif = (top - this.top);
        this.top = top;
        
        if (getAnchors().isBottom()){
            height -= dif;
            return true;
        } else {
            bottom -= dif;
            return false;
        }
    }

    private boolean changeWidth(int width){
        int dif = (width - this.width);
        this.width = width;
        
        if (getAnchors().isLeft()){
            if (!getAnchors().isRight()){
                right -= dif;
                return false;
            }
        } 
        else if (getAnchors().isRight()) {
            left -= dif;
            return true;
        }
        
        int metade = dif/2;
        if (left > right){
            left -= metade;
            right -= dif - metade;
            return metade != 0;
        } else {
            left -= dif - metade;
            right -= metade;
            return true;
        }
    }

    private boolean changeHeight(int height){
        int dif = (height - this.height);
        Anchors anchors = getAnchors();
        this.height = height;
        
        if (anchors.isTop()){
            if (!anchors.isBottom()){
                bottom -= dif;
                return false;
            }
        } 
        else if (anchors.isBottom()) {
            top -= dif;
            return true;
        }
        
        int metade = dif/2;
        if (top > bottom){
            top -= metade;
            bottom -= dif - metade;
            return metade != 0;
        } else {
            top -= dif - metade;
            bottom -= metade;
            return true;
        }
    }

    private boolean changeRight(int right){
        int dif = (right - this.right);
        this.right = right;
        
        if (getAnchors().isLeft()){
            width -= dif;
            return true;
        } else {
            left -= dif;
            return false;
        }
    }

    private boolean changeBottom(int bottom){
        int dif = (bottom - this.bottom);
        this.bottom = bottom;
        
        if (getAnchors().isTop()){
            height -= dif;
            return true;
        } else {
            top -= dif;
            return false;
        }
    }

    protected void dimensionChanged() {
    }

    protected void positionChanged() {
    }

    protected void boundsChanged() {
    }
    
    @Override
    public Display clone() {
        try {
            return (Display) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

}
